/*
 * Lenovo Group
 * Copyright (c) 2015-2016 All Rights Reserved.
 * 
 * Project Name:itsm-gateway
 * Create Time: 2016年1月30日 下午3:02:56
 */
package com.lenovo.lmrp.server.itsmgateway.adapter;

import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Named;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.inject.Binder;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.lenovo.lmrp.api.model.entity.TicketSystemPO;
import com.lenovo.lmrp.api.model.entity.TicketSystemPO.TicketSystemStatus;
import com.lenovo.sdc.framework.dao.ADaoFactory;
import com.lenovo.sdc.framework.dao.ICommonDAO;
import com.lenovo.sdc.framework.dao.RecordFilter;
import com.lenovo.sdc.framework.util.AttributeBean;
import com.lenovo.sdc.framework.util.CommonUtil;
import com.lenovo.sdc.framework.util.Condition;
import com.lenovo.sdc.framework.util.JSONHelper;
import com.lenovo.sdc.framework.util.exception.ErrorCode;
import com.lenovo.sdc.framework.util.exception.TraceableException;

/**
 * 
 * @author wujin
 * @version v1.0
 */
public abstract class AItsmAdapterFactory {
    private static AItsmAdapterFactory factory;
    @Inject
    private Injector injector;
    
    @Inject
    protected AttributeBean sysParams;
    
    private Map<String, AItsmAdapter> adapterCache;

    public AItsmAdapterFactory() {
        adapterCache = new ConcurrentHashMap<String, AItsmAdapter>();
    }
    
    public static AItsmAdapterFactory getFactory() {
        return factory;
    }
    
    @Inject
    public static void setFactory(AItsmAdapterFactory factory) {
        AItsmAdapterFactory.factory = factory;
    }
    
    /**
     * load and instant all enabled ITSM Adapter.
     */
    public abstract void initialize();
    
    /**
     * obtain adapter instance by ITSM Code that is generated during ITSM register.
     * 
     * @param itsmcode
     * @return
     */
    public final AItsmAdapter getAdapter(String itsmcode) {
        AItsmAdapter adapter = adapterCache.get(itsmcode);
        if (Objects.isNull(adapter)) {
            adapter = createAdapter(itsmcode);
            registerAdapter(itsmcode, adapter);
        }
        
        return adapter;
    }
    
    /**
     * cache adapter instance
     * 
     * @param itsmcode
     * @param adapter
     */
    protected void registerAdapter(String itsmcode, AItsmAdapter adapter) {
        adapterCache.put(itsmcode, adapter);
        
        if (Objects.nonNull(injector))
            injector.injectMembers(adapter);
    }
    
    /**
     * create adapter instance according to itsm configuration parameters that stored in DB
     * 
     * @param itsmcode
     * @return
     */
    protected abstract AItsmAdapter createAdapter(String itsmcode);
    
    
    public static class AItsmAdapterModule implements Module {

        @Override
        public void configure(Binder binder) {
            binder.bind(AItsmAdapterFactory.class).to(AdapterFactoryImpl.class).in(Scopes.SINGLETON);
            //binder.bind(AItsmAdapter.class).annotatedWith(Names.named("lenovo-ms")).to(LenovoMSAdapter.class);
            
            binder.requestStaticInjection(AItsmAdapterFactory.class);
        }
    }
    
    @Named("default")
    static class AdapterFactoryImpl extends AItsmAdapterFactory {
        
        public AdapterFactoryImpl() {
            super();
        }

        /**
         * load all enabled registered ITSM system and create adapter instance one by one
         */
        public void initialize() {
            try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
                RecordFilter filter = new RecordFilter();
                filter.addCondition(new Condition<String, Object>("status", TicketSystemStatus.ENABLED));
                List<TicketSystemPO> pos = dao.filteByCondition(TicketSystemPO.class, filter);
                for (TicketSystemPO po : pos) {
                    try {
                        registerAdapter(po.getItsmCode(), createAdapter(po));
                    }
                    catch (Exception exp) {
                        TraceableException.writeLog(ErrorCode.COMM_ERROR,
                            "failed to instant itsm adapter:" + po.getItsmCode(), exp);
                    }
                }
            }
            catch (Exception exp) {
                TraceableException.writeLog(ErrorCode.DB_QUERY_FAILED, "failed to query TicketSystem record", exp);
            }
        }

        /**
         * create adapter instance base on registered itsm system parameter:adapter.class, 
         * that class should extend from AItsmAdapter and has constructor with AttributeBean input paratmer
         * 
         * @param itsmcode
         * @return
         * @throws Exception
         */
        protected AItsmAdapter createAdapter(String itsmcode) {
            try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
                TicketSystemPO po = dao.get(TicketSystemPO.class, itsmcode);
                if (Objects.isNull(po)) {
                    throw new TraceableException(ErrorCode.DB_RECORD_NOTFOUND, " itsmcode= " + itsmcode);
                }
                
                return createAdapter(po);
            }
            catch (TraceableException exp) {
                throw exp;
            }
            catch (Exception exp) {
                throw new TraceableException(ErrorCode.COMM_ERROR, exp);
            }
        }
        
        private AItsmAdapter createAdapter(TicketSystemPO po) throws Exception {
            // create adapter parameter collection from db field value
            Map<String, Object> params = JSONHelper.fromJSON(po.getParams(), new TypeReference<Map<String, Object>>(){});
            AttributeBean bean = new AttributeBean(params);
            
            // find out adapter class name, if not found throw exception
            String clzName = bean.getAttribute(AItsmAdapter.ADAPTER_CLASS, "");
            if (CommonUtil.isNullOrEmpty(clzName))
                throw new TraceableException(ErrorCode.COMM_PARAM_MISSED, "adapter.class for " + po.getItsmCode());

            // copy system parameters to adapter parameter collection
            bean.addAttribute(AItsmAdapter.ADAPTER_ITSMCOMDE, po.getItsmCode());
            Map<String, String> sysMap = JSONHelper.fromJSON(sysParams.dump(), new TypeReference<Map<String, String>>(){});
            sysMap.entrySet().forEach(entry -> bean.addAttribute(entry.getKey(), entry.getValue()));
            
            // create adapter instance with adapter parameters + system parameters
            Class<?> clz = Class.forName(clzName);
            Constructor<?> constructor = clz.getDeclaredConstructor(AttributeBean.class);
            return (AItsmAdapter) constructor.newInstance(bean);
        }
    }
}
